﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using NjamNjam.Model.Entities;
using FluentMongo.Linq;
using System.Linq;

namespace NjaNjam.MongoRepository
{
    public class MongoRepository
    {
        private const string VrstePonudeCollection = "VrstaPonude";
        private const string VrsteRestoranaCollection = "VrsteRestorana";
        private const string RestoraniCollection = "Restorani";
        private const string PonudaCollection = "Ponuda";
        private readonly string m_ConnectionString = "mongodb://zeks:j0v4n4@ds029807.mongolab.com:29807";
        private readonly string m_DatabaseName = "njamnjam";
        private static MongoDatabase _db;

        public MongoRepository()
        {
            SetupDatabase();
        }

        public MongoRepository(string connectionString, string databaseName)
        {
            m_ConnectionString = connectionString;
            m_DatabaseName = databaseName;
            SetupDatabase();
        }

        public void Index()
        {
            var restoraniCollection = _db.GetCollection(RestoraniCollection);
            restoraniCollection.DropAllIndexes();
            restoraniCollection.EnsureIndex(IndexKeys.GeoSpatial("Lokacija"));
            restoraniCollection.EnsureIndex(IndexKeys.Ascending("Ponuda"));
            restoraniCollection.EnsureIndex(IndexKeys.Ascending("VrstaRestorana"));
        }

        public IEnumerable<VrstaRestorana> VrsteRestorana()
        {
            return _db.GetCollection<VrstaRestorana>(VrsteRestoranaCollection).FindAll().OrderBy(x => x.Naziv);
        } 

        public IEnumerable<Restoran> Restorani()
        {
            return _db.GetCollection<Restoran>(RestoraniCollection).FindAll().ToList().OrderBy(x => x.Naziv);
        }

        public IEnumerable<Restoran> VratiRestoraneUOkolini(double latitude, double longitude, double distanceInKilometers)
        {
            var query = Query.And(Query.Near("Lokacija", latitude, longitude, distanceInKilometers/111), Query.EQ("Aktivan", BsonBoolean.True));
            return _db.GetCollection<Restoran>(RestoraniCollection).Find(query)
                .ToList().OrderBy(x => x.Naziv);
        }

        public IEnumerable<Restoran> VratiRestoraneTipa(VrstaRestorana tipRestorana)
        {
            if (tipRestorana != null)
            {
                var collection = _db.GetCollection<Restoran>(RestoraniCollection);
                var query = Query.EQ("VrstaRestorana._id", new BsonInt64(tipRestorana.Id));
                return collection.Find(query).SetFields("Id", "Naziv", "Adresa", "Mesto").OrderBy(x => x.Naziv);
            }
            return Restorani();

        }

        public IEnumerable<Restoran> VratiRestoraneSaRadnimVremenom(DateTime od, DateTime @do)
        {
            var query = Query.And(Query.GTE("RadnoVremeOd", new BsonDateTime(od)),
                                  Query.LTE("RadnoVremeDo", new BsonDateTime(@do)),
                                  Query.EQ("Aktivan", BsonBoolean.True));
            return _db.GetCollection<Restoran>(RestoraniCollection).Find(query).ToList().OrderBy(x => x.Naziv);
        }

        public IEnumerable<Restoran> PretraziRestorane(double? lat, double? lon, double? dist, VrstaRestorana vrstaRestorana, Ponuda ponuda)
        {
            var coll = _db.GetCollection<Restoran>(RestoraniCollection);
            IMongoQuery query = Query.EQ("Aktivan", BsonBoolean.True);
            if (lat.HasValue && lon.HasValue && dist.HasValue)
            {
                query = Query.And(query, Query.Near("Lokacija", lat.Value, lon.Value, dist.Value/111));
            }
            if (vrstaRestorana != null)
            {
                query = Query.And(query, Query.EQ("VrstaRestorana._id", new BsonInt64(vrstaRestorana.Id)));
            }
            if (ponuda != null)
            {
                query = Query.And(query, Query.ElemMatch("Ponuda", Query.EQ("Ponuda._id", new BsonInt64(ponuda.Id))));
            }
            if (query == null) return new Restoran[0];
            return coll.Find(query).SetFields("Id", "Naziv", "Latituda", "Longituda", "Adresa", "Mesto");
        }

        public Restoran VratiRestoranPoId(long id)
        {
            var coll = _db.GetCollection<Restoran>(RestoraniCollection);
            return coll.Find(Query.EQ("_id", new BsonInt64(id))).SetFields("Id", "Naziv", "VrstaRestorana", "Adresa", "Mesto", "KratakOpis", "BrojTelefona", "Latituda", "Longituda").SingleOrDefault();
        }

        public IEnumerable<Ponuda> Ponuda()
        {
            return _db.GetCollection<Ponuda>(PonudaCollection).FindAll().OrderBy(x => x.Naziv);
        }

        public IEnumerable<Ponuda> VratiPonuduVrste(long idVrste)
        {
            var coll = _db.GetCollection<Ponuda>(PonudaCollection);
            var query = Query.ElemMatch("VrstePonude", Query.EQ("_id", new BsonInt64(idVrste)));
            return coll.Find(query).SetFields("Id", "Naziv", "Opis").OrderBy(x => x.Naziv);
        }

        public IEnumerable<Restoran> VratiRestoraneSaPonudom(Ponuda ponuda)
        {
            return _db.GetCollection<Restoran>(RestoraniCollection).AsQueryable().Where(
                x => x.Aktivan && x.Ponuda.Any(y => y.Ponuda.Id == ponuda.Id)).OrderBy(x => x.Naziv);
        }

        public IEnumerable<VrstaPonude> VrstePonude()
        {
            return _db.GetCollection<VrstaPonude>(VrstePonudeCollection).FindAll().ToList().OrderBy(x => x.Naziv);
        }

        public void Sacuvaj(VrstaRestorana vrstaRestorana)
        {
            _db.GetCollection<VrstaRestorana>(VrsteRestoranaCollection).Save(vrstaRestorana);
        }

        public void Sacuvaj(Restoran restoran)
        {
            _db.GetCollection<Restoran>(RestoraniCollection).Save(restoran);
        }

        public void Sacuvaj(Ponuda ponuda)
        {
            _db.GetCollection<Ponuda>(PonudaCollection).Save(ponuda);
        }

        public void Sacuvaj(VrstaPonude vrstaPonude)
        {
            _db.GetCollection<VrstaPonude>(VrstePonudeCollection).Save(vrstaPonude);
        }

        public void Obrisi(VrstaRestorana vrstaRestorana)
        {
            _db.GetCollection<VrstaRestorana>(VrsteRestoranaCollection).Remove(Query.EQ("_id", new BsonInt64(vrstaRestorana.Id)));
        }

        public void Obrisi(Restoran restoran)
        {
            _db.GetCollection<Restoran>(RestoraniCollection).Remove(Query.EQ("_id", new BsonInt64(restoran.Id)));
        }

        public void Obrisi(Ponuda ponuda)
        {
            _db.GetCollection<Ponuda>(PonudaCollection).Remove(Query.EQ("_id", new BsonInt64(ponuda.Id)));
        }

        public void Obrisi(VrstaPonude vrstaPonude)
        {
            _db.GetCollection<VrstaPonude>(VrstePonudeCollection).Remove(Query.EQ("_id", new BsonInt64(vrstaPonude.Id)));
        }

        public void ObrisiBazu()
        {
            _db.Drop();
        }

        private void SetupDatabase()
        {
            if (_db == null)
            {
                var server = MongoServer.Create(m_ConnectionString);
                _db = server.GetDatabase(m_DatabaseName);
            }
        }

        public IEnumerable<PonudaRestorana> VrstePonuduRestorana(long idRestorana)
        {
            var coll = _db.GetCollection<Restoran>(RestoraniCollection);
            var query = Query.EQ("_id", new BsonInt64(idRestorana));
            return coll.Find(query).SetFields("Ponuda").Single().Ponuda;
        }

        public IEnumerable<VrstaPonude> VratiVrstePonudeUOkolini(double? latituda, double? longituda, double? razdaljina)
        {
            var sveVrstePonude = _db.GetCollection<VrstaPonude>(VrstePonudeCollection).FindAll();
            var coll = _db.GetCollection<Restoran>(RestoraniCollection);
            if (!latituda.HasValue || !longituda.HasValue || !razdaljina.HasValue)
            {
                return sveVrstePonude;
            }
            var query = RestoraniUOkoliniQuery(latituda.Value, longituda.Value, razdaljina.Value);
            var restorani = coll.Find(query).SetFields("Ponuda");
            var vrstePonude = new HashSet<VrstaPonude>();
            var vpDictionary = sveVrstePonude.ToDictionary(x => x.Id);
            foreach (var restoran in restorani)
            {
                var vp = restoran.Ponuda.SelectMany(x => x.Ponuda.VrstePonude);
                foreach (var i in vp)
                {
                    if (vpDictionary.ContainsKey(i.Id))
                    {
                        vrstePonude.Add(vpDictionary[i.Id]);    
                    }
                }
            }
            return vrstePonude.OrderBy(x => x.Naziv);
        }

        public IEnumerable<Ponuda> VratiPonuduUOkolini(long idVrstePonude, double? latituda, double? longituda, double? razdaljina)
        {
            if (!latituda.HasValue || !longituda.HasValue || !razdaljina.HasValue)
            {
                return VratiPonuduVrste(idVrstePonude);
            }
            var query = RestoraniUOkoliniQuery(latituda.Value, longituda.Value, razdaljina.Value);
            var coll = _db.GetCollection<Restoran>(RestoraniCollection);
            var ponuda = coll.Find(query).SetFields("Ponuda").SelectMany(x => x.Ponuda).Select(x => x.Ponuda);
            return ponuda.Where(p => p.VrstePonude.Any(vp => vp.Id == idVrstePonude)).Distinct().OrderBy(x => x.Naziv);
        }

        private static QueryComplete RestoraniUOkoliniQuery(double latituda, double longituda, double razdaljina)
        {
            return Query.And(Query.EQ("Aktivan", BsonBoolean.True), Query.Near("Lokacija", latituda, longituda, razdaljina / 111));
        }

        public IEnumerable<VrstaRestorana> VratiVrsteRestoranaUOkolini(double? latituda, double? longituda, double? dist)
        {
            if (!latituda.HasValue || !longituda.HasValue || !dist.HasValue)
            {
                return VrsteRestorana();
            }
            var query = RestoraniUOkoliniQuery(latituda.Value, longituda.Value, dist.Value);
            var coll = _db.GetCollection<Restoran>(RestoraniCollection);
            return coll.Find(query).SetFields("VrstaRestorana").Select(x => x.VrstaRestorana).Distinct().OrderBy(x => x.Naziv);
        }

        public IEnumerable<Restoran> VratiRestoraneTipaUOkolini(long idTipa, double? latituda, double? longituda, double? dist)
        {
            if (!latituda.HasValue || !longituda.HasValue || !dist.HasValue)
            {
                return VratiRestoraneTipa(new VrstaRestorana{Id = idTipa});
            }
            var query = RestoraniUOkoliniQuery(latituda.Value, longituda.Value, dist.Value);
            query = Query.And(query, Query.EQ("VrstaRestorana._id", new BsonInt64(idTipa)));
            var coll = _db.GetCollection<Restoran>(RestoraniCollection);
            return coll.Find(query).OrderBy(x => x.Naziv);
        }
    }
}